package com.hiabr.iep.admin.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hiabr.iep.admin.api.UserService;
import com.hiabr.iep.admin.mapper.UserMapper;
import com.hiabr.iep.admin.po.User;
import com.hiabr.iep.admin.vo.req.LoginReqVo;
import com.hiabr.iep.admin.vo.req.RefreshTokenReqVo;
import com.hiabr.iep.admin.vo.req.UserPageReq;
import com.hiabr.iep.admin.vo.resp.LoginRespVo;
import com.hiabr.iep.common.domain.constant.CommonConstant;
import com.hiabr.iep.common.domain.constant.RedisConstant;
import com.hiabr.iep.common.domain.enums.YesOrNo;
import com.hiabr.iep.common.domain.vo.req.BaseAdminReq;
import com.hiabr.iep.common.domain.vo.resp.ErrorType;
import com.hiabr.iep.common.redis.service.RedisService;
import com.hiabr.iep.common.utils.RequestUtil;
import com.hiabr.iep.common.utils.bean.BeanUtil;
import com.hiabr.iep.common.utils.random.UUIDUtil;
import com.hiabr.iep.common.web.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author jie huang
 * @since 2023-06-28
 */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Value("${admin.password}")
    private String defaultPassword;
    @Value("${token.expiresIn}")
    private long tokenExpiresIn;
    @Value("${token.refreshExpiresIn}")
    private long refreshTokenExpiresIn;
    @Value("${token.multi-terminal:true}")
    private boolean multiTerminal;

    @Lazy
    @Resource
    private RedisService redisService;


    @Override
    public LoginRespVo login(LoginReqVo req) {
        QueryWrapper<User> query = new QueryWrapper<>();
        query.lambda().eq(User::getUsername, req.getUsername());
        User userPo = this.getOne(query);
        // 用户名或密码错误
        if (userPo == null || !userPo.getPassword().equals(DigestUtils.md5Hex(req.getPassword()))) {
            throw new BusinessException(ErrorType.ERROR_LOGIN);
        }
        // 账户被禁用
        if (userPo.getStatus().equals(YesOrNo.NO.getCode())) {
            throw new BusinessException(ErrorType.ERROR_LOGIN.getCode(), "账户已被禁用");
        }
        BaseAdminReq user = BeanUtil.copyProperties(userPo, BaseAdminReq.class);
        user.setUserId(userPo.getId());
        return generateToken(user);
    }

    @Override
    public void logout() {
        String token = RequestUtil.getHeader(CommonConstant.TOKEN);
        this.clearAdminToken(token);
    }

    @Override
    public LoginRespVo refreshToken(RefreshTokenReqVo req) {
        BaseAdminReq value = (BaseAdminReq) redisService
                .get(RedisConstant.TOKEN_REFRESH_ADMIN + req.getRefreshToken());
        if (value == null) {
            throw new BusinessException(ErrorType.ERROR_TOKEN);
        }
        return generateToken(value);
    }

    /**
     * 生成token公用方法
     *
     * @param user
     * @return
     */
    private LoginRespVo generateToken(BaseAdminReq user) {
        // TODO 没有使用lua脚本，存在数据不一致风险
        String token = UUIDUtil.generateUUID();
        String refreshToken = UUIDUtil.generateUUID();
        String onlineAdminKey = RedisConstant.ONLINE_ADMIN + token;
        Object old = redisService.getAndSet(onlineAdminKey, token + RedisConstant.CONNECTOR + refreshToken);
        // 同一时刻只允许一个终端在线
        if (!multiTerminal && old != null) {
            String[] split = old.toString().split(RedisConstant.CONNECTOR);
            redisService.del(RedisConstant.TOKEN_ADMIN + split[0]);
            redisService.del(RedisConstant.TOKEN_REFRESH_ADMIN + split[1]);
        }
        redisService.expire(onlineAdminKey, refreshTokenExpiresIn);
        redisService.set(RedisConstant.TOKEN_ADMIN + token, user, tokenExpiresIn);
        redisService.set(RedisConstant.TOKEN_REFRESH_ADMIN + refreshToken, user, refreshTokenExpiresIn);
        return LoginRespVo.builder().token(token).tokenExpiresIn(tokenExpiresIn).refreshToken(refreshToken)
                .refreshTokenExpiresIn(refreshTokenExpiresIn).build();
    }

    /**
     * 清除用户token
     *
     * @param token
     */
    private void clearAdminToken(String token) {
        Object old = redisService.get(RedisConstant.ONLINE_ADMIN + token);
        if (old != null) {
            String[] split = old.toString().split(RedisConstant.CONNECTOR);
            // 清除token和refreshToken
            redisService.del(RedisConstant.TOKEN_ADMIN + split[0]);
            redisService.del(RedisConstant.TOKEN_REFRESH_ADMIN + split[1]);
            // 清除个人信息
            redisService.del(RedisConstant.ONLINE_ADMIN + token);
        }
    }

    @Override
    public IPage<User> pageUser(UserPageReq req) {
        int pageNum = req.getPageNum();
        int pageSize = req.getPageSize();
        Page<User> page = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(User::getCreateTime);


        // 模糊查询 账号，姓名，手机号，所属单位
        if (StringUtils.isNotEmpty(req.getKeywords())) {
            queryWrapper.and(i -> i.like(User::getUsername, req.getKeywords())
                    .or()
                    .like(User::getNickname, req.getKeywords())
                    .or()
                    .like(User::getMobile, req.getKeywords())
                    .or()
                    .like(User::getOrganization, req.getKeywords()));
        }

        return this.page(page, queryWrapper);
    }
}
